Merge "Defer cookie block checks to resolve a circular dependency"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 1 Jul 2019 11:09:45 +0000 (11:09 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 1 Jul 2019 11:09:45 +0000 (11:09 +0000)
includes/block/BlockManager.php
includes/user/User.php
tests/phpunit/includes/block/BlockManagerTest.php

index dc07364..c89145c 100644 (file)
@@ -21,6 +21,7 @@
 namespace MediaWiki\Block;
 
 use DateTime;
+use DeferredUpdates;
 use IP;
 use MediaWiki\User\UserIdentity;
 use MWCryptHash;
@@ -431,26 +432,38 @@ class BlockManager {
         * @param User $user
         */
        public function trackBlockWithCookie( User $user ) {
-               $block = $user->getBlock();
                $request = $user->getRequest();
-               $response = $request->response();
-               $isAnon = $user->isAnon();
-
-               if ( $block && $request->getCookie( 'BlockID' ) === null ) {
-                       if ( $block instanceof CompositeBlock ) {
-                               // TODO: Improve on simply tracking the first trackable block (T225654)
-                               foreach ( $block->getOriginalBlocks() as $originalBlock ) {
-                                       if ( $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon ) ) {
-                                               $this->setBlockCookie( $originalBlock, $response );
-                                               return;
+               if ( $request->getCookie( 'BlockID' ) !== null ) {
+                       // User already has a block cookie
+                       return;
+               }
+
+               // Defer checks until the user has been fully loaded to avoid circular dependency
+               // of User on itself
+               DeferredUpdates::addCallableUpdate(
+                       function () use ( $user, $request ) {
+                               $block = $user->getBlock();
+                               $response = $request->response();
+                               $isAnon = $user->isAnon();
+
+                               if ( $block ) {
+                                       if ( $block instanceof CompositeBlock ) {
+                                               // TODO: Improve on simply tracking the first trackable block (T225654)
+                                               foreach ( $block->getOriginalBlocks() as $originalBlock ) {
+                                                       if ( $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon ) ) {
+                                                               $this->setBlockCookie( $originalBlock, $response );
+                                                               return;
+                                                       }
+                                               }
+                                       } else {
+                                               if ( $this->shouldTrackBlockWithCookie( $block, $isAnon ) ) {
+                                                       $this->setBlockCookie( $block, $response );
+                                               }
                                        }
                                }
-                       } else {
-                               if ( $this->shouldTrackBlockWithCookie( $block, $isAnon ) ) {
-                                       $this->setBlockCookie( $block, $response );
-                               }
-                       }
-               }
+                       },
+                       DeferredUpdates::PRESEND
+               );
        }
 
        /**
index 97d4702..f7dcb93 100644 (file)
@@ -1280,12 +1280,11 @@ class User implements IDBAccessObject, UserIdentity {
                $user = $session->getUser();
                if ( $user->isLoggedIn() ) {
                        $this->loadFromUserObject( $user );
-                       if ( $user->getBlock() ) {
-                               // If this user is autoblocked, set a cookie to track the block. This has to be done on
-                               // every session load, because an autoblocked editor might not edit again from the same
-                               // IP address after being blocked.
-                               MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
-                       }
+
+                       // If this user is autoblocked, set a cookie to track the block. This has to be done on
+                       // every session load, because an autoblocked editor might not edit again from the same
+                       // IP address after being blocked.
+                       MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
 
                        // Other code expects these to be set in the session, so set them.
                        $session->set( 'wsUserID', $this->getId() );
index 40fe4c8..892add9 100644 (file)
@@ -323,4 +323,62 @@ class BlockManagerTest extends MediaWikiTestCase {
 
                $this->assertSame( 2, count( $method->invoke( $blockManager, $blocks ) ) );
        }
+
+       /**
+        * @covers ::trackBlockWithCookie
+        * @dataProvider provideTrackBlockWithCookie
+        * @param bool $expectCookieSet
+        * @param bool $hasCookie
+        * @param bool $isBlocked
+        */
+       public function testTrackBlockWithCookie( $expectCookieSet, $hasCookie, $isBlocked ) {
+               $blockID = 123;
+               $this->setMwGlobals( 'wgCookiePrefix', '' );
+
+               $request = new FauxRequest();
+               if ( $hasCookie ) {
+                       $request->setCookie( 'BlockID', 'the value does not matter' );
+               }
+
+               if ( $isBlocked ) {
+                       $block = $this->getMockBuilder( DatabaseBlock::class )
+                               ->setMethods( [ 'getType', 'getId' ] )
+                               ->getMock();
+                       $block->method( 'getType' )
+                               ->willReturn( DatabaseBlock::TYPE_IP );
+                       $block->method( 'getId' )
+                               ->willReturn( $blockID );
+               } else {
+                       $block = null;
+               }
+
+               $user = $this->getMockBuilder( User::class )
+                       ->setMethods( [ 'getBlock', 'getRequest' ] )
+                       ->getMock();
+               $user->method( 'getBlock' )
+                       ->willReturn( $block );
+               $user->method( 'getRequest' )
+                       ->willReturn( $request );
+               /** @var User $user */
+
+               // Although the block cookie is set via DeferredUpdates, in command line mode updates are
+               // processed immediately
+               $blockManager = $this->getBlockManager( [] );
+               $blockManager->trackBlockWithCookie( $user );
+
+               /** @var FauxResponse $response */
+               $response = $request->response();
+               $this->assertCount( $expectCookieSet ? 1 : 0, $response->getCookies() );
+               $this->assertEquals( $expectCookieSet ? $blockID : null, $response->getCookie( 'BlockID' ) );
+       }
+
+       public function provideTrackBlockWithCookie() {
+               return [
+                       // $expectCookieSet, $hasCookie, $isBlocked
+                       [ false, false, false ],
+                       [ false, true, false ],
+                       [ true, false, true ],
+                       [ false, true, true ],
+               ];
+       }
 }